package com.dbflow5.processor.definition.column;

import com.dbflow5.StringUtils;
import com.dbflow5.annotation.ConflictAction;
import com.dbflow5.processor.ProcessorManager;
import com.dbflow5.processor.definition.behavior.ComplexColumnBehavior;
import com.dbflow5.processor.utils.ElementUtility;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.TypeName;

import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;

/**
 * Description:
 */
public class ReferenceDefinition {
    private final ProcessorManager manager;
    private final String foreignKeyFieldName;
    private final String foreignKeyElementName;
    private final ColumnDefinition referencedColumn;
    private final ReferenceColumnDefinition referenceColumnDefinition;
    private final int referenceCount;
    private final String localColumnName;
    public ConflictAction onNullConflict;
    public String defaultValue;
    private final ClassName typeConverterClassName;
    private final TypeMirror typeConverterTypeMirror;

    public ReferenceDefinition(ProcessorManager manager, String foreignKeyFieldName, String foreignKeyElementName, ColumnDefinition referencedColumn,
                               ReferenceColumnDefinition referenceColumnDefinition, int referenceCount, String localColumnName, ConflictAction onNullConflict,
                               String defaultValue, ClassName typeConverterClassName, TypeMirror typeConverterTypeMirror) {
        this.manager = manager;
        this.foreignKeyFieldName = foreignKeyFieldName;
        this.foreignKeyElementName = foreignKeyElementName;
        this.referencedColumn = referencedColumn;
        this.referenceColumnDefinition = referenceColumnDefinition;
        this.referenceCount = referenceCount;
        this.localColumnName = localColumnName;
        this.onNullConflict = onNullConflict;
        this.defaultValue = defaultValue;
        this.typeConverterClassName = typeConverterClassName;
        this.typeConverterTypeMirror = typeConverterTypeMirror;

        foreignColumnName = referencedColumn != null? referencedColumn.columnName : "";
        columnClassName = referencedColumn != null? referencedColumn.elementTypeName : null;
        isReferencedFieldPrivate = referencedColumn != null && referencedColumn.columnAccessor instanceof ColumnAccessor.PrivateScopeColumnAccessor;

        init();
    }


    public String columnName() {
        if(!StringUtils.isNullOrEmpty(localColumnName)) {
            return localColumnName;
        }else if(!referenceColumnDefinition.type.isPrimaryField() || referenceCount > 0) {
            return foreignKeyFieldName + "_" + referencedColumn.columnName;
        }else {
            return foreignKeyFieldName;
        }
    }

    public String foreignColumnName;
    public TypeName columnClassName;

    public boolean notNull() {
        return onNullConflict != ConflictAction.NONE;
    }

    private final boolean isReferencedFieldPrivate ;
    private boolean isReferencedFieldPackagePrivate;

    public CodeBlock creationStatement() {
        return DefinitionUtils.getCreationStatement(columnClassName, complexColumnBehavior.wrapperTypeName, columnName()).build();
    }

    public String primaryKeyName() {
        return StringUtils.quote(columnName());
    }

    public boolean hasTypeConverter() {
        return complexColumnBehavior.hasTypeConverter;
    }

    private ColumnAccessor columnAccessor;
    private ComplexColumnBehavior complexColumnBehavior;

    public ForeignKeyAccessCombiner.PartialLoadFromCursorAccessCombiner partialAccessor;
    public ForeignKeyAccessCombiner.ForeignKeyAccessField primaryReferenceField;
    public ForeignKeyAccessCombiner.ForeignKeyAccessField contentValuesField;
    public ForeignKeyAccessCombiner.ForeignKeyAccessField sqliteStatementField;

    private void init() {
        boolean isPackagePrivate = ElementUtility.isPackagePrivate(referencedColumn.element);
        boolean isPackagePrivateNotInSamePackage = isPackagePrivate &&
            !ElementUtility.isInSamePackage(manager, referencedColumn.element,
                referenceColumnDefinition.element);

        isReferencedFieldPackagePrivate = referencedColumn.columnAccessor instanceof ColumnAccessor.PackagePrivateScopeColumnAccessor
            || isPackagePrivateNotInSamePackage;

        String tableClassName = ClassName.get((TypeElement)referencedColumn.element.getEnclosingElement()).simpleName();
        ColumnAccessor.GetterSetter getterSetter = new ColumnAccessor.GetterSetter() {
                @Override
                public String getterName() {
                    return referencedColumn.column != null? referencedColumn.column.getterName() : "";
                }

                @Override
                public String setterName() {
                    return referencedColumn.column != null? referencedColumn.column.setterName() : "";
                }
        };

        if(isReferencedFieldPrivate) {
            columnAccessor = new ColumnAccessor.PrivateScopeColumnAccessor(foreignKeyElementName, getterSetter, false, "");
        } else if(isReferencedFieldPackagePrivate) {
            ColumnAccessor.PackagePrivateScopeColumnAccessor accessor = new ColumnAccessor.PackagePrivateScopeColumnAccessor(foreignKeyElementName, referencedColumn.packageName, tableClassName);
            ColumnAccessor.PackagePrivateScopeColumnAccessor.putElement(accessor.helperClassName, foreignKeyElementName);

            columnAccessor = accessor;
        } else {
            columnAccessor = new ColumnAccessor.VisibleScopeColumnAccessor(foreignKeyElementName);
        }

        complexColumnBehavior = new ComplexColumnBehavior(
            columnClassName,
            referenceColumnDefinition,
            referencedColumn,
            referencedColumn.complexColumnBehavior.hasCustomConverter,
            typeConverterClassName,
            typeConverterTypeMirror,
            manager
        );

        ColumnAccessCombiner.Combiner combiner = new ColumnAccessCombiner.Combiner(columnAccessor, columnClassName, complexColumnBehavior.wrapperAccessor,
            complexColumnBehavior.wrapperTypeName, complexColumnBehavior.subWrapperAccessor, referenceColumnDefinition.elementName);

        partialAccessor = new ForeignKeyAccessCombiner.PartialLoadFromCursorAccessCombiner(
            columnName(),
            foreignColumnName,
            columnClassName,
            referenceColumnDefinition.entityDefinition.cursorHandlingBehavior().orderedCursorLookup,
            columnAccessor,
            complexColumnBehavior.wrapperAccessor,
            complexColumnBehavior.wrapperTypeName
        );

        CodeBlock defaultValue = referenceColumnDefinition.getDefaultValueBlock(this.defaultValue, columnClassName);
        primaryReferenceField = new ForeignKeyAccessCombiner.ForeignKeyAccessField(columnName(),
            new ColumnAccessCombiner.PrimaryReferenceAccessCombiner(combiner), defaultValue);

        contentValuesField = new ForeignKeyAccessCombiner.ForeignKeyAccessField(columnName(),
            new ColumnAccessCombiner.ContentValuesCombiner(combiner), defaultValue);

        sqliteStatementField = new ForeignKeyAccessCombiner.ForeignKeyAccessField("",
            new ColumnAccessCombiner.SqliteStatementAccessCombiner(combiner), defaultValue);
    }

}
